package com.csx.micrometer.sentinel.core;

import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.csp.sentinel.metric.extension.MetricExtension;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
import com.csx.micrometer.common.util.MetricsUtil;
import com.csx.micrometer.sentinel.constant.SentinelMetricsConstant;
import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.Tags;

import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;

/**
 * 数据监控扩展点实现
 * @author cuisongxu
 * @date 2023/1/19 10:19
 */
public class SentinelMetricsExtension implements MetricExtension {

    /**
     * 记录业务处理线程数.
     * key -> resource; value -> AtomicLong
     */
    private final ConcurrentHashMap<String, AtomicLong> currentThreadNum = new ConcurrentHashMap<>();

    @Override
    public void addPass(String resource, int n, Object... args) {
        Tag tag = Tag.of(SentinelMetricsConstant.API, resource);
        MetricsUtil.counter(SentinelMetricsConstant.SENTINEL_PASS_REQUEST_TOTAL, Tags.of(tag)).increment();
        MetricsUtil.counter(SentinelMetricsConstant.SENTINEL_REQUEST_TOTAL, Tags.of(tag)).increment();
    }

    @Override
    public void addBlock(String resource, int n, String origin, BlockException blockException, Object... args) {

        Tag tag = Tag.of(SentinelMetricsConstant.API, resource);
        MetricsUtil.counter(SentinelMetricsConstant.SENTINEL_BLOCK_REQUEST_TOTAL, Tags.of(tag)).increment(n);;
        MetricsUtil.counter(SentinelMetricsConstant.SENTINEL_REQUEST_TOTAL, Tags.of(tag)).increment(n);

        // 触发的熔断
        if(blockException instanceof DegradeException) {
            MetricsUtil.counter(SentinelMetricsConstant.SENTINEL_DEGRADE_REQUEST_TOTAL, Tags.of(tag)).increment(n);
        }else { // 触发的限流
            MetricsUtil.counter(SentinelMetricsConstant.SENTINEL_PARAM_FLOW_REQUEST_TOTAL, Tags.of(tag)).increment();
        }
    }

    @Override
    public void addSuccess(String resource, int n, Object... args) {

        Tag tag = Tag.of(SentinelMetricsConstant.API, resource);
        MetricsUtil.counter(SentinelMetricsConstant.SENTINEL_SUCCESS_REQUEST_TOTAL, Tags.of(tag)).increment(n);
    }

    @Override
    public void addException(String resource, int n, Throwable throwable) {
        Tag tag = Tag.of(SentinelMetricsConstant.API, resource);
        MetricsUtil.counter(SentinelMetricsConstant.SENTINEL_EXCEPTION_REQUEST_TOTAL, Tags.of(tag)).increment(n);
    }

    @Override
    public void addRt(String resource, long responseTime, Object... args) {
        Tag tag = Tag.of(SentinelMetricsConstant.API, resource);
        MetricsUtil.histogram(SentinelMetricsConstant.SENTINEL_INVOKE_RESPONSE_TIME, responseTime, Tags.of(tag));
        Set<DegradeRule> degradeRules = DegradeRuleManager.getRulesOfResource(resource);

        if(!CollectionUtil.isEmpty(degradeRules))  {
            for (DegradeRule degradeRule : degradeRules) {

                // 跳过非超时熔断的熔断规则
                if(!SentinelMetricsConstant.SENTINEL_DEGRADE_MODEL.equals(String.valueOf(degradeRule.getGrade()))) {
                    continue;
                }

                // count 在 rt模式下是一个阈值,单位为ms。超过此值则判定为慢调用。
                if(responseTime > degradeRule.getCount()) {
                    MetricsUtil.counter(SentinelMetricsConstant.SENTINEL_TIMEOUT_REQUEST_TOTAL, Tags.of(tag)).increment();
                }
            }
        }
    }

    @Override
    public void increaseThreadNum(String resource, Object... args) {
        Tag tag = Tag.of(SentinelMetricsConstant.API, resource);
        AtomicLong threadNum = this.currentThreadNum.computeIfAbsent(resource, (count) -> new AtomicLong());
        threadNum.incrementAndGet();
        MetricsUtil.gauge(SentinelMetricsConstant.SENTINEL_CURRENT_THREAD_NUMBER, threadNum, Tags.of(tag));
    }

    @Override
    public void decreaseThreadNum(String resource, Object... args) {
        Tag tag = Tag.of(SentinelMetricsConstant.API, resource);
        AtomicLong threadNum = this.currentThreadNum.computeIfPresent(resource, (resourceIn, count) -> {
            count.decrementAndGet();
            return count;
        });
        MetricsUtil.gauge(SentinelMetricsConstant.SENTINEL_CURRENT_THREAD_NUMBER, threadNum, Tags.of(tag));
    }
}
